using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Gko.Utils
{
	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
	public class TaskQueue : DisposableBase
	{
		private readonly List<ITask> tasks = new List<ITask>();
		private readonly ReaderWriterLockSlim locker = new ReaderWriterLockSlim();

		public TaskQueue(IEnumerable<ITask> newTasks)
		{
			this.Add(newTasks);
			this.AddDisposed(this.locker);
		}

		public TaskQueue(params ITask[] newTasks)
			: this(newTasks.AsEnumerable())
		{
		}

		public void StartTasks()
		{
			var thread = new Thread(this.Work)
			{
				IsBackground = true
			};
#if !SILVERLIGHT
			thread.SetApartmentState(ApartmentState.MTA);
#endif
			thread.Start();
		}

		public void Clear()
		{
			using (locker.Write()) {
				this.tasks.Clear();
			}
		}

		public void Add(IEnumerable<ITask> newTasks)
		{
			if (newTasks == null) {
				throw new ArgumentNullException("newTasks");
			}
			using (locker.Write()) {
				this.tasks.AddRange(newTasks);
				this.tasks.Sort(Compare);
			}
		}

		private static int Compare(ITask task1, ITask task2)
		{
			return ReferenceEquals(task1, task2) ? 0 : task2.Priority.CompareTo(task1.Priority);
		}

		public void Add(params ITask[] newTasks)
		{
			if (newTasks == null) {
				throw new ArgumentNullException("newTasks");
			}
			this.Add(newTasks.AsEnumerable());
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void Work()
		{
			var result = ProceedResult.None;
			while (true) {
				var task = this.Current;
				if(task == null) {
					break;
				}
				this.TaskStarting.Raise(this, new TaskStartingEventArgs(task.Name));
				try {
					result = task.OnProceed();
				} catch (Exception e) {
					result = this.OnExceptionOccurred(e) ? ProceedResult.RepeatRequested : ProceedResult.Failed;
				}
				this.HandleResult(task, ref result);
			}
			this.OnComplete(result);
		}

		public ITask Current
		{
			get
			{
				using (locker.Read()) {
					return this.tasks.FirstOrDefault();
				}
			}
		}

		private void HandleResult(ITask task, ref ProceedResult result)
		{
			switch (result) {
				case ProceedResult.None:
					goto default;
				case ProceedResult.Completed:
					using (locker.Write()) {
						this.tasks.Remove(task);
					}
					break;
				case ProceedResult.Canceled:
					this.Clear();
					break;
				case ProceedResult.Aborted:
					this.Clear();
					break;
				case ProceedResult.RepeatRequested:
					break;
				case ProceedResult.Failed:
					this.Clear();
					break;
				case ProceedResult.PauseRequested:
					result = task.WaitForResume();
					this.HandleResult(task, ref result);
					break;
				default:
#if SILVERLIGHT
					this.OnExceptionOccurred(new ArgumentOutOfRangeException("result", "Invalid proceed result. Actual value: {0}".Format(result)));
#else
					this.OnExceptionOccurred(new ArgumentOutOfRangeException("result", result, "Invalid proceed result"));
#endif
					result = ProceedResult.Failed;
					this.Clear();
					break;
			}
		}

		private void OnComplete(ProceedResult proceedResult)
		{
			this.AllTasksCompleted.Raise(this, new TaskCompletedEventArgs(proceedResult));
		}

		private bool OnExceptionOccurred(Exception exception)
		{
			var e = new ExceptionOccurredEventArgs(exception);
			this.ExceptionOccurred.Raise(this, e);
			return e.IsHandled;
		}

		public event EventHandler<TaskStartingEventArgs> TaskStarting;
		public event EventHandler<TaskCompletedEventArgs> AllTasksCompleted;
		public event EventHandler<ExceptionOccurredEventArgs> ExceptionOccurred;

	}
}